package net.kucoe.grin.internal.interaction;

import java.util.Map;
import java.util.concurrent.locks.*;

import net.kucoe.grin.context.ContextActor;

/**
 * Interaction method that continuing until one of participants ends it.
 * 
 * @author Vitaliy Basyuk
 * @param <T> interaction type
 */
public abstract class Interaction<T> {
    
    private final ContextActor caller;
    private final ContextActor callee;
    protected T result;
    private ContinueRequest continueRequest;
    private boolean complete;
    private Throwable lastError;
    private InteractionCallback callback;
    private final Lock lock = new ReentrantLock();
    private final Condition interactionLock = lock.newCondition();
    
    /**
     * Constructs Interaction.
     * 
     * @param caller
     * @param callee
     */
    public Interaction(final ContextActor caller, final ContextActor callee) {
        this.caller = caller;
        this.callee = callee;
    }
    
    /**
     * Continue interaction with parameters defined.
     * 
     * @param params
     */
    public abstract void interactWith(Map<String, Object> params);
    
    /**
     * Interaction is ready.
     */
    public abstract void onComplete();
    
    /**
     * Interaction is failed.
     * 
     * @param error
     */
    public abstract void onFail(Throwable error);
    
    /**
     * Returns the result ResultFuture<T>.
     * 
     * @return the result ResultFuture<T>.
     */
    public T getResult() {
        return result;
    }
    
    protected void doContinue(final ContinueRequest request) {
        continueRequest = request;
        callContinue(request);
    }
    
    private void callContinue(final ContinueRequest request) {
        if (callback != null) {
            callback.onContinue(request);
            continueRequest = null;
        }
    }
    
    /**
     * Completes interaction
     */
    public void complete() {
        complete = true;
        callComplete();
        onComplete();
    }
    
    private void callComplete() {
        if (callback != null) {
            callback.onComplete();
            continueRequest = null;
        }
    }
    
    /**
     * Fails interaction
     * 
     * @param error
     */
    public void fail(final Throwable error) {
        lastError = error;
        complete = true;
        callFail(error);
        onFail(error);
    }
    
    private void callFail(final Throwable error) {
        if (callback != null) {
            callback.onFail(error);
            continueRequest = null;
        }
    }
    
    /**
     * Define {@link InteractionCallback} for interaction continuation.
     * 
     * @param callback
     */
    public void addCallback(final InteractionCallback callback) {
        this.callback = callback;
        if (continueRequest != null) {
            callContinue(continueRequest);
        }
        if (complete) {
            callComplete();
        }
        if (lastError != null) {
            callFail(lastError);
        }
    }
    
    /**
     * Waits thread until signal. <br/>
     * Note: if interaction is completed thread will not be waiting.
     */
    public void await() {
        lock.lock();
        if (!complete) {
            interactionLock.awaitUninterruptibly();
        }
        lock.unlock();
    }
    
    /**
     * Signals waiting threads.
     */
    public void signal() {
        lock.lock();
        interactionLock.signalAll();
        lock.unlock();
    }
}
